import { LOG_IO, MMAP_BLOCK_BITS, MMAP_BLOCK_SIZE, MMAP_MAX } from "./const.js";
import { h } from "./lib.js";
import { dbg_assert, dbg_log } from "./log.js";

// 仅用于类型定义
import { CPU } from "./cpu.js";

// 启用所有IO端口读写操作的日志记录，输出非常详细
const LOG_ALL_IO = false;

/**
 * ISA IO总线实现
 * 设备在此注册其IO端口，用于处理端口读写和内存映射IO操作
 *
 * @constructor
 * @param {CPU} cpu - CPU实例，用于访问内存映射相关功能
 */
export function IO(cpu)
{
    /** @const */
    this.ports = []; // IO端口数组，存储所有端口的读写函数

    /** @const @type {CPU} */
    this.cpu = cpu; // CPU引用，用于内存映射操作

    // 初始化所有65536个IO端口，创建空的端口条目
    for(var i = 0; i < 0x10000; i++)
    {
        this.ports[i] = this.create_empty_entry();
    }

    var memory_size = cpu.memory_size[0];

    // 初始化内存映射数组，避免稀疏数组
    for(var i = 0; (i << MMAP_BLOCK_BITS) < memory_size; i++)
    {
        cpu.memory_map_read8[i] = cpu.memory_map_write8[i] = undefined;
        cpu.memory_map_read32[i] = cpu.memory_map_write32[i] = undefined;
    }

    // 注册未映射内存空间的处理函数，用于处理超出物理内存范围的读写
    this.mmap_register(memory_size, MMAP_MAX - memory_size,
        function(addr) {
            // 读取未映射内存空间
            dbg_log("读取未映射内存空间, addr=" + h(addr >>> 0, 8), LOG_IO);
            return 0xFF;
        },
        function(addr, value) {
            // 写入未映射内存空间
            dbg_log("写入未映射内存空间, addr=" + h(addr >>> 0, 8) + " value=" + h(value, 2), LOG_IO);
        },
        function(addr) {
            dbg_log("读取未映射内存空间, addr=" + h(addr >>> 0, 8), LOG_IO);
            return -1;
        },
        function(addr, value) {
            dbg_log("写入未映射内存空间, addr=" + h(addr >>> 0, 8) + " value=" + h(value >>> 0, 8), LOG_IO);
        }
    );
}

/**
 * 创建空的IO端口条目
 * @returns {Object} 包含默认读写函数的端口条目对象
 */
IO.prototype.create_empty_entry = function()
{
    return {
        read8: this.empty_port_read8,    // 8位端口读函数
        read16: this.empty_port_read16,  // 16位端口读函数
        read32: this.empty_port_read32,  // 32位端口读函数

        write8: this.empty_port_write,   // 8位端口写函数
        write16: this.empty_port_write,  // 16位端口写函数
        write32: this.empty_port_write,  // 32位端口写函数

        device: undefined,               // 设备引用，默认为undefined
    };
}

/**
 * 默认的8位空端口读函数
 * @returns {number} 默认返回0xFF
 */
IO.prototype.empty_port_read8 = function()
{
    return 0xFF; // 未注册端口的默认8位读返回值
};

/**
 * 默认的16位空端口读函数
 * @returns {number} 默认返回0xFFFF
 */
IO.prototype.empty_port_read16 = function()
{
    return 0xFFFF; // 未注册端口的默认16位读返回值
};

/**
 * 默认的32位空端口读函数
 * @returns {number} 默认返回-1 (0xFFFFFFFF)
 */
IO.prototype.empty_port_read32 = function()
{
    return -1; // 未注册端口的默认32位读返回值
};

/**
 * 默认的空端口写函数
 * @param {number} x - 要写入的值（被忽略）
 */
IO.prototype.empty_port_write = function(x)
{
    // 未注册端口的写操作默认不做任何事情
};


/**
 * 注册IO端口的读函数
 * @param {number} port_addr - IO端口地址
 * @param {Object} device - 设备对象
 * @param {function(number):number=} r8 - 可选的8位读函数
 * @param {function(number):number=} r16 - 可选的16位读函数
 * @param {function(number):number=} r32 - 可选的32位读函数
 */
IO.prototype.register_read = function(port_addr, device, r8, r16, r32)
{
    dbg_assert(typeof port_addr === "number");
    dbg_assert(typeof device === "object");
    dbg_assert(!r8 || typeof r8 === "function");
    dbg_assert(!r16 || typeof r16 === "function");
    dbg_assert(!r32 || typeof r32 === "function");
    dbg_assert(r8 || r16 || r32);

    if(DEBUG)
    {
        // 在调试模式下，如果没有提供特定位宽的读函数，创建一个失败函数用于检测端口冲突
        var fail = function(n) {
            dbg_assert(false, "端口读操作冲突" + n + " " + h(port_addr, 4) + " (" + device.name + ")");
            return -1 >>> (32 - n) | 0;
        };
        if(!r8) r8 = fail.bind(this, 8);
        if(!r16) r16 = fail.bind(this, 16);
        if(!r32) r32 = fail.bind(this, 32);
    }

    // 注册提供的读函数到对应端口
    if(r8) this.ports[port_addr].read8 = r8;
    if(r16) this.ports[port_addr].read16 = r16;
    if(r32) this.ports[port_addr].read32 = r32;
    this.ports[port_addr].device = device; // 关联设备对象
};

/**
 * 注册IO端口的写函数
 * @param {number} port_addr - IO端口地址
 * @param {Object} device - 设备对象
 * @param {function(number)=} w8 - 可选的8位写函数
 * @param {function(number)=} w16 - 可选的16位写函数
 * @param {function(number)=} w32 - 可选的32位写函数
 */
IO.prototype.register_write = function(port_addr, device, w8, w16, w32)
{
    dbg_assert(typeof port_addr === "number");
    dbg_assert(typeof device === "object");
    dbg_assert(!w8 || typeof w8 === "function");
    dbg_assert(!w16 || typeof w16 === "function");
    dbg_assert(!w32 || typeof w32 === "function");
    dbg_assert(w8 || w16 || w32);

    if(DEBUG)
    {
        // 在调试模式下，如果没有提供特定位宽的写函数，创建一个失败函数用于检测端口冲突
        var fail = function(n) {
            dbg_assert(false, "端口写操作冲突" + n + " " + h(port_addr) + " (" + device.name + ")");
        };
        if(!w8) w8 = fail.bind(this, 8);
        if(!w16) w16 = fail.bind(this, 16);
        if(!w32) w32 = fail.bind(this, 32);
    }

    // 注册提供的写函数到对应端口
    if(w8) this.ports[port_addr].write8 = w8;
    if(w16) this.ports[port_addr].write16 = w16;
    if(w32) this.ports[port_addr].write32 = w32;
    this.ports[port_addr].device = device; // 关联设备对象
};

/**
 * 注册连续IO端口的读函数，自动处理不同位宽的访问
 *
 * > 任何两个连续的8位端口都可以被当作一个16位端口；
 * > 四个连续的8位端口都可以被当作一个32位端口
 * > http://css.csail.mit.edu/6.858/2012/readings/i386/s08_01.htm
 *
 * 注意：此信息并非对所有端口都正确，但通过以下函数可以处理不同情况
 *
 * 注册2或4个连续8位端口的读函数，以及相应的16位和32位读函数
 *
 * @param {number} port_addr - 起始IO端口地址
 * @param {!Object} device - 设备对象
 * @param {function():number} r8_1 - 第一个8位端口的读函数
 * @param {function():number} r8_2 - 第二个8位端口的读函数
 * @param {function():number=} r8_3 - 可选的第三个8位端口的读函数
 * @param {function():number=} r8_4 - 可选的第四个8位端口的读函数
 */
IO.prototype.register_read_consecutive = function(port_addr, device, r8_1, r8_2, r8_3, r8_4)
{
    dbg_assert(arguments.length === 4 || arguments.length === 6);

    // 创建16位读函数，组合两个连续的8位读结果
    function r16_1()
    {
        return r8_1.call(this) |  // 低8位
                r8_2.call(this) << 8; // 高8位
    }
    
    // 创建第二个16位读函数（仅在提供四个8位函数时使用）
    function r16_2()
    {
        return r8_3.call(this) |  // 低8位
                r8_4.call(this) << 8; // 高8位
    }
    
    // 创建32位读函数，组合四个连续的8位读结果
    function r32()
    {
        return r8_1.call(this) |      // 第0位
                r8_2.call(this) << 8 |  // 第8位
                r8_3.call(this) << 16 | // 第16位
                r8_4.call(this) << 24;  // 第24位
    }

    // 根据提供的8位函数数量，注册不同的端口组合
    if(r8_3 && r8_4)  // 四个8位端口
    {
        this.register_read(port_addr, device, r8_1, r16_1, r32);       // 第0端口：8位、16位、32位
        this.register_read(port_addr + 1, device, r8_2);               // 第1端口：仅8位
        this.register_read(port_addr + 2, device, r8_3, r16_2);        // 第2端口：8位、16位
        this.register_read(port_addr + 3, device, r8_4);               // 第3端口：仅8位
    }
    else  // 两个8位端口
    {
        this.register_read(port_addr, device, r8_1, r16_1);            // 第0端口：8位、16位
        this.register_read(port_addr + 1, device, r8_2);               // 第1端口：仅8位
    }
};

/**
 * 注册连续IO端口的写函数，自动处理不同位宽的访问
 *
 * @param {number} port_addr - 起始IO端口地址
 * @param {!Object} device - 设备对象
 * @param {function(number)} w8_1 - 第一个8位端口的写函数
 * @param {function(number)} w8_2 - 第二个8位端口的写函数
 * @param {function(number)=} w8_3 - 可选的第三个8位端口的写函数
 * @param {function(number)=} w8_4 - 可选的第四个8位端口的写函数
 */
IO.prototype.register_write_consecutive = function(port_addr, device, w8_1, w8_2, w8_3, w8_4)
{
    dbg_assert(arguments.length === 4 || arguments.length === 6);

    // 创建16位写函数，将数据拆分到两个连续的8位端口
    function w16_1(data)
    {
        w8_1.call(this, data & 0xFF);       // 写入低8位
        w8_2.call(this, data >> 8 & 0xFF);  // 写入高8位
    }
    
    // 创建第二个16位写函数（仅在提供四个8位函数时使用）
    function w16_2(data)
    {
        w8_3.call(this, data & 0xFF);       // 写入低8位
        w8_4.call(this, data >> 8 & 0xFF);  // 写入高8位
    }
    
    // 创建32位写函数，将数据拆分到四个连续的8位端口
    function w32(data)
    {
        w8_1.call(this, data & 0xFF);        // 写入第0-7位
        w8_2.call(this, data >> 8 & 0xFF);   // 写入第8-15位
        w8_3.call(this, data >> 16 & 0xFF);  // 写入第16-23位
        w8_4.call(this, data >>> 24);        // 写入第24-31位（无符号右移）
    }

    // 根据提供的8位函数数量，注册不同的端口组合
    if(w8_3 && w8_4)  // 四个8位端口
    {
        this.register_write(port_addr,     device, w8_1, w16_1, w32);  // 第0端口：8位、16位、32位
        this.register_write(port_addr + 1, device, w8_2);              // 第1端口：仅8位
        this.register_write(port_addr + 2, device, w8_3, w16_2);       // 第2端口：8位、16位
        this.register_write(port_addr + 3, device, w8_4);              // 第3端口：仅8位
    }
    else  // 两个8位端口
    {
        this.register_write(port_addr,     device, w8_1, w16_1);       // 第0端口：8位、16位
        this.register_write(port_addr + 1, device, w8_2);              // 第1端口：仅8位
    }
};

/**
 * 内存映射32位读操作的垫片函数
 * 当没有直接提供32位读函数时，通过组合四个连续的8位读操作来实现32位读
 *
 * @param {number} addr - 要读取的内存地址
 * @returns {number} 32位读取结果
 */
IO.prototype.mmap_read32_shim = function(addr)
{
    var aligned_addr = addr >>> MMAP_BLOCK_BITS;  // 计算内存块对齐地址
    var fn = this.cpu.memory_map_read8[aligned_addr];  // 获取对应的8位读函数

    // 组合四个连续的8位读操作，构造32位结果
    return fn(addr) | fn(addr + 1) << 8 |
            fn(addr + 2) << 16 | fn(addr + 3) << 24;
};

/**
 * 内存映射32位写操作的垫片函数
 * 当没有直接提供32位写函数时，通过拆分32位值到四个连续的8位写操作来实现32位写
 *
 * @param {number} addr - 要写入的内存地址
 * @param {number} value - 要写入的32位值
 */
IO.prototype.mmap_write32_shim = function(addr, value)
{
    var aligned_addr = addr >>> MMAP_BLOCK_BITS;  // 计算内存块对齐地址
    var fn = this.cpu.memory_map_write8[aligned_addr];  // 获取对应的8位写函数

    // 将32位值拆分并写入四个连续的8位地址
    fn(addr, value & 0xFF);        // 写入第0-7位
    fn(addr + 1, value >> 8 & 0xFF);   // 写入第8-15位
    fn(addr + 2, value >> 16 & 0xFF);  // 写入第16-23位
    fn(addr + 3, value >>> 24);        // 写入第24-31位（无符号右移）
};

/**
 * 注册内存映射I/O区域
 *
 * @param {number} addr - 起始内存地址（必须按MMAP_BLOCK_SIZE对齐）
 * @param {number} size - 映射区域大小（必须是MMAP_BLOCK_SIZE的倍数）
 * @param {*} read_func8 - 8位读函数
 * @param {*} write_func8 - 8位写函数
 * @param {*=} read_func32 - 可选的32位读函数，未提供则使用垫片函数
 * @param {*=} write_func32 - 可选的32位写函数，未提供则使用垫片函数
 */
IO.prototype.mmap_register = function(addr, size, read_func8, write_func8, read_func32, write_func32)
{
    dbg_log("mmap_register addr=" + h(addr >>> 0, 8) + " size=" + h(size, 8), LOG_IO);

    // 验证地址和大小是否按块大小对齐
    dbg_assert((addr & MMAP_BLOCK_SIZE - 1) === 0);
    dbg_assert(size && (size & MMAP_BLOCK_SIZE - 1) === 0);

    // 如果没有提供32位读写函数，则使用垫片函数
    if(!read_func32)
        read_func32 = this.mmap_read32_shim.bind(this);

    if(!write_func32)
        write_func32 = this.mmap_write32_shim.bind(this);

    var aligned_addr = addr >>> MMAP_BLOCK_BITS;  // 计算起始块索引

    // 为每个内存块注册读写函数
    for(; size > 0; aligned_addr++)
    {
        this.cpu.memory_map_read8[aligned_addr] = read_func8;
        this.cpu.memory_map_write8[aligned_addr] = write_func8;
        this.cpu.memory_map_read32[aligned_addr] = read_func32;
        this.cpu.memory_map_write32[aligned_addr] = write_func32;

        size -= MMAP_BLOCK_SIZE;
    }
};


/**
 * 执行8位端口写操作
 *
 * @param {number} port_addr - IO端口地址
 * @param {number} data - 要写入的8位数据
 * @returns {*} 写操作的返回值（通常为undefined）
 */
IO.prototype.port_write8 = function(port_addr, data)
{
    var entry = this.ports[port_addr];  // 获取端口条目

    // 记录未注册端口或启用所有IO日志时的写操作
    if(entry.write8 === this.empty_port_write || LOG_ALL_IO)
    {
        dbg_log(
            "write8端口 #" + h(port_addr, 4) + " <- " + h(data, 2) + this.get_port_description(port_addr),
            LOG_IO
        );
    }
    
    // 调用设备的写函数，绑定到正确的设备上下文
    return entry.write8.call(entry.device, data);
};

/**
 * 执行16位端口写操作
 *
 * @param {number} port_addr - IO端口地址
 * @param {number} data - 要写入的16位数据
 * @returns {*} 写操作的返回值（通常为undefined）
 */
IO.prototype.port_write16 = function(port_addr, data)
{
    var entry = this.ports[port_addr];  // 获取端口条目

    // 记录未注册端口或启用所有IO日志时的写操作
    if(entry.write16 === this.empty_port_write || LOG_ALL_IO)
    {
        dbg_log(
            "write16端口 #" + h(port_addr, 4) + " <- " + h(data, 4) + this.get_port_description(port_addr),
            LOG_IO
        );
    }
    
    // 调用设备的写函数，绑定到正确的设备上下文
    return entry.write16.call(entry.device, data);
};

/**
 * 执行32位端口写操作
 *
 * @param {number} port_addr - IO端口地址
 * @param {number} data - 要写入的32位数据
 * @returns {*} 写操作的返回值（通常为undefined）
 */
IO.prototype.port_write32 = function(port_addr, data)
{
    var entry = this.ports[port_addr];  // 获取端口条目

    // 记录未注册端口或启用所有IO日志时的写操作
    if(entry.write32 === this.empty_port_write || LOG_ALL_IO)
    {
        dbg_log(
            "write32端口 #" + h(port_addr, 4) + " <- " + h(data >>> 0, 8) + this.get_port_description(port_addr),
            LOG_IO
        );
    }
    
    // 调用设备的写函数，绑定到正确的设备上下文
    return entry.write32.call(entry.device, data);
};

/**
 * 执行8位端口读操作
 *
 * @param {number} port_addr - IO端口地址
 * @returns {number} 读取的8位数据（范围0-255）
 */
IO.prototype.port_read8 = function(port_addr)
{
    var entry = this.ports[port_addr];  // 获取端口条目

    // 记录未注册端口或启用所有IO日志时的读操作
    if(entry.read8 === this.empty_port_read8 || LOG_ALL_IO)
    {
        dbg_log(
            "read8端口  #" + h(port_addr, 4) + this.get_port_description(port_addr),
            LOG_IO
        );
    }
    
    // 调用设备的读函数，绑定到正确的设备上下文
    var value = entry.read8.call(entry.device, port_addr);
    
    // 验证返回值类型和范围是否正确
    dbg_assert(typeof value === "number");
    if(value < 0 || value >= 0x100) dbg_assert(false, "8位端口返回了超出范围的值: " + h(port_addr));
    
    return value;
};

/**
 * 执行16位端口读操作
 *
 * @param {number} port_addr - IO端口地址
 * @returns {number} 读取的16位数据（范围0-65535）
 */
IO.prototype.port_read16 = function(port_addr)
{
    var entry = this.ports[port_addr];  // 获取端口条目

    // 记录未注册端口或启用所有IO日志时的读操作
    if(entry.read16 === this.empty_port_read16 || LOG_ALL_IO)
    {
        dbg_log(
            "read16端口  #" + h(port_addr, 4) + this.get_port_description(port_addr),
            LOG_IO
        );
    }
    
    // 调用设备的读函数，绑定到正确的设备上下文
    var value = entry.read16.call(entry.device, port_addr);
    
    // 验证返回值类型和范围是否正确
    dbg_assert(typeof value === "number");
    if(value < 0 || value >= 0x10000) dbg_assert(false, "16位端口返回了超出范围的值: " + h(port_addr));
    
    return value;
};

/**
 * 执行32位端口读操作
 *
 * @param {number} port_addr - IO端口地址
 * @returns {number} 读取的32位数据
 */
IO.prototype.port_read32 = function(port_addr)
{
    var entry = this.ports[port_addr];  // 获取端口条目

    // 记录未注册端口或启用所有IO日志时的读操作
    if(entry.read32 === this.empty_port_read32 || LOG_ALL_IO)
    {
        dbg_log(
            "read32端口  #" + h(port_addr, 4) + this.get_port_description(port_addr),
            LOG_IO
        );
    }
    
    // 调用设备的读函数，绑定到正确的设备上下文
    var value = entry.read32.call(entry.device, port_addr);
    
    // 验证返回值是否为32位整数
    dbg_assert((value | 0) === value);
    
    return value;
};

// 常见调试端口列表（基于seabios ioport.h）
// 用于日志输出时显示端口的中文描述
var debug_port_list = {
    0x0004: "PORT_DMA_ADDR_2",             // DMA通道2地址寄存器
    0x0005: "PORT_DMA_CNT_2",              // DMA通道2计数寄存器
    0x000a: "PORT_DMA1_MASK_REG",          // DMA控制器1掩码寄存器
    0x000b: "PORT_DMA1_MODE_REG",          // DMA控制器1模式寄存器
    0x000c: "PORT_DMA1_CLEAR_FF_REG",      // DMA控制器1清除触发器寄存器
    0x000d: "PORT_DMA1_MASTER_CLEAR",      // DMA控制器1主清除寄存器
    0x0020: "PORT_PIC1_CMD",               // 主PIC命令寄存器
    0x0021: "PORT_PIC1_DATA",              // 主PIC数据寄存器
    0x0040: "PORT_PIT_COUNTER0",           // PIT计数器0
    0x0041: "PORT_PIT_COUNTER1",           // PIT计数器1
    0x0042: "PORT_PIT_COUNTER2",           // PIT计数器2
    0x0043: "PORT_PIT_MODE",               // PIT模式寄存器
    0x0060: "PORT_PS2_DATA",               // PS/2数据端口
    0x0061: "PORT_PS2_CTRLB",              // PS/2控制寄存器B
    0x0064: "PORT_PS2_STATUS",             // PS/2状态寄存器
    0x0070: "PORT_CMOS_INDEX",             // CMOS索引端口
    0x0071: "PORT_CMOS_DATA",              // CMOS数据端口
    0x0080: "PORT_DIAG",                   // 诊断端口
    0x0081: "PORT_DMA_PAGE_2",             // DMA页面寄存器2
    0x0092: "PORT_A20",                    // A20门控端口
    0x00a0: "PORT_PIC2_CMD",               // 从PIC命令寄存器
    0x00a1: "PORT_PIC2_DATA",              // 从PIC数据寄存器
    0x00b2: "PORT_SMI_CMD",                // SMI命令端口
    0x00b3: "PORT_SMI_STATUS",             // SMI状态端口
    0x00d4: "PORT_DMA2_MASK_REG",          // DMA控制器2掩码寄存器
    0x00d6: "PORT_DMA2_MODE_REG",          // DMA控制器2模式寄存器
    0x00da: "PORT_DMA2_MASTER_CLEAR",      // DMA控制器2主清除寄存器
    0x00f0: "PORT_MATH_CLEAR",             // 数学协处理器清除端口
    0x0170: "PORT_ATA2_CMD_BASE",          // 第二个ATA设备命令基址
    0x01f0: "PORT_ATA1_CMD_BASE",          // 第一个ATA设备命令基址
    0x0278: "PORT_LPT2",                   // 并行端口2
    0x02e8: "PORT_SERIAL4",                // 串行端口4
    0x02f8: "PORT_SERIAL2",                // 串行端口2
    0x0374: "PORT_ATA2_CTRL_BASE",         // 第二个ATA设备控制基址
    0x0378: "PORT_LPT1",                   // 并行端口1
    0x03e8: "PORT_SERIAL3",                // 串行端口3
    //0x03f4: "PORT_ATA1_CTRL_BASE",         // 第一个ATA设备控制基址
    0x03f0: "PORT_FD_BASE",                // 软盘控制器基址
    0x03f2: "PORT_FD_DOR",                 // 软盘数字输出寄存器
    0x03f4: "PORT_FD_STATUS",              // 软盘状态寄存器
    0x03f5: "PORT_FD_DATA",                // 软盘数据寄存器
    0x03f6: "PORT_HD_DATA",                // 硬盘数据寄存器
    0x03f7: "PORT_FD_DIR",                 // 软盘方向控制寄存器
    0x03f8: "PORT_SERIAL1",                // 串行端口1
    0x0cf8: "PORT_PCI_CMD",                // PCI配置命令端口
    0x0cf9: "PORT_PCI_REBOOT",             // PCI重启端口
    0x0cfc: "PORT_PCI_DATA",               // PCI配置数据端口
    0x0402: "PORT_BIOS_DEBUG",             // BIOS调试端口
    0x0510: "PORT_QEMU_CFG_CTL",           // QEMU配置控制端口
    0x0511: "PORT_QEMU_CFG_DATA",          // QEMU配置数据端口
    0xb000: "PORT_ACPI_PM_BASE",            // ACPI电源管理基址
    0xb100: "PORT_SMB_BASE",                // SMBus基址
    0x8900: "PORT_BIOS_APM"                 // BIOS APM端口
};

/**
 * 获取IO端口的描述信息
 *
 * @param {number} addr - IO端口地址
 * @returns {string} 端口描述字符串，如果端口不在列表中则返回空字符串
 */
IO.prototype.get_port_description = function(addr)
{
    if(debug_port_list[addr])
    {
        return "  (" + debug_port_list[addr] + ")";
    }
    else
    {
        return "";
    }
};
